package data.crawling;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import entities.vocabulary.Display;

public class KBBuilder extends Builder {

	private String filename;
	private static HashMap<String, String> vendorindex = new HashMap<String, String>();
	private static Map<String, String> vendorInstId = new HashMap<String, String>();
	private final String targetfile = "dpreviewRawData_v2.txt";
	private static Set<String> features = new HashSet<String>();
	private static int totalIns = 0, totalAxiom = 0, totalCA = 0, totalRA = 0;
	
	private static final String[] MANUS = {
		"Agfa", "Canon", "Casio", "Contax", "Epson", "FujiFilm", "HP", "Kodak", "Konica Minolta", "Kyocera", 
		"Leica", "Nikon", "Olympus", "Panasonic", "Pentax", "Ricoh", "Samsung", "Sanyo", "Sigma", 
		"Sony", "Toshiba"
	};
	
	/**
	 * When man is "zero-sized", we build everything, otherwise build specified brands.
	 * @param man
	 */
	public void buildKBox(String[] mans, int maxInst){
		Builder.maxRandomInstancePerPriceModel = maxInst;
		if(mans== null || mans.length <= 0 ){
			//builds all brands
			this.buildKBox(MANUS, maxInst);
			return;
		}
		//builds the specified
		String start = "DPC", ext = ".kbox";
		if(mans.length == MANUS.length){
			this.filename = start+ext;
		}else if(mans.length <= 3){
			String mm = "";
			for(String s:mans){
				mm += s+"_";
			}
			this.filename = mm+start+ext;
		}
		else{
			this.filename = start+"_"+mans.length+ext;
		}
		StringBuilder tbuilder = new StringBuilder(mans.length*20*150);
		StringBuilder abuilder = new StringBuilder(mans.length*20*20);
		for(int i=0; i<mans.length; i++){
			this.buildKBox(mans[i], tbuilder, abuilder);
			System.out.println("Analyzing cameras of brand: "+mans[i]);
		}
		//build sellers and manus
		this.buildVendorInstances(tbuilder, abuilder);
		//output the file
		FileWriter fstream = null;
	    BufferedWriter out = null;
		try {
			fstream = new FileWriter(this.filename, false);
			out = new BufferedWriter(fstream);
			this.listInfo(out);
			String intro = "\n######\n# TBox \n######\n";
			out.append(intro);
			out.append(tbuilder.toString());
			intro = "\n######\n# ABox \n######\n";
			out.append(intro);
			out.append(abuilder.toString());
			out.newLine();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	//get feature info
	private void listInfo(BufferedWriter out) throws IOException {
		String pt = "#######################\n" +
				"## This KB is populated with data from DPreview.com and Amazon.com.\n" +
				"## Total # of Axioms: " + totalAxiom + "\n" +
				"## Total # of Concept Assertions: " + totalCA + "\n" +
				"## Total # of Role Assertions: " + totalRA + "\n" + 
				"## Total # of Instances: " + totalIns + "\n" + 
				"#######################\n" +
				"##\t\t\t\t\t\tFeatures ("+features.size()+") available in this KB:\n"
				;
		StringBuilder sb = new StringBuilder(features.size() * 15);
		int itemPerLine = 0;
		sb.append("## ");
		String sep = "; ";
		for(String fs:features){
			if(++itemPerLine % 5 == 0 ){
				sb.append("\n## "+fs+sep);	
			}else{
				sb.append(fs+sep);
			}
		}
		sb.append("\n#######################");
		out.append(pt+sb.toString());
		out.newLine();
	}

	private void buildKBox(String man, StringBuilder tb, StringBuilder ab){
		BufferedReader in = null;
		try {
			in = new BufferedReader(new FileReader(this.targetfile));
			String strLine;
			tb.append("\n#########################\n" +
					"### Implications about "+man+" Camera Models\n" +
					"#########################\n");
			while((strLine = in.readLine()) != null){
				if (strLine == null || strLine.trim().equals("")
	        			||strLine.startsWith(Display.COMMENTS)){
	        		continue;
	        	}
				//check if this line is a model of specified brands
				String model = strLine.substring(strLine.indexOf(PageCrawler.identifier)+PageCrawler.identifier.length(), 
						strLine.indexOf(PageCrawler.pair_separator));
				//find short name: the instance name
				int startpos = strLine.indexOf(Builder.CAMERASHORTNAME)+Builder.CAMERASHORTNAME.length()
					+PageCrawler.identifier.length();
				int endpos = strLine.indexOf(PageCrawler.pair_separator, startpos);
				String instName = strLine.substring(startpos, endpos);
				if(! model.trim().toLowerCase().startsWith(man.trim().toLowerCase())){
					continue;
				}
				parseModel(instName, model, strLine, tb, ab);
			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	private void parseModel(String instName, String camModel, String strLine, StringBuilder tb, StringBuilder ab) {
		String[] pairs = strLine.split("\\"+PageCrawler.pair_separator);
		String pr = "(implies "+normalizeString(camModel)+" (and ";
		for(int i=0; i<pairs.length; i++){
			String pair = pairs[i];
			if(pair.trim()==""){
				continue;
			}
			String ret = parsePair(instName, camModel,pair, tb, ab);
			if(ret.trim().length()>0){
				pr += " "+ret+" ";
			}
		}
		//get manufacturer info
		String man = camModel.trim();
		man = man.substring(0, man.indexOf(" "));
		if(man.equalsIgnoreCase("Konica")){
			man = "Konica Minolta";
		}
		man = normalizeString(man);
		pr += " (forall (role "+Builder.hasMan+") "+man+") ";
		pr += " ))";
		totalAxiom ++;
		tb.append(pr+"\n");
	}

	private String parsePair(String instName, String camModel, String pair, StringBuilder tb, StringBuilder ab) {
		String ret = "";
		String[] ps = pair.split("\\"+PageCrawler.identifier);
		String key = null, value = null;
		if(ps.length<2){
			key = ps[0].trim();
		}else{
			key = ps[0].trim();
			value = ps[1].trim();
		}
//		if(value.trim().length()<1){
//			return ret;
//		}
		if(key.equalsIgnoreCase(Builder.PRICEVENDORINFO)){
			parsePriceVendor(instName, camModel, value, tb, ab);
		}else if(key.equalsIgnoreCase(Builder.CAMERASHORTNAME)
				|| key.equalsIgnoreCase(Builder.CAMERANAME)){
			return ret;
		}else if(key.equalsIgnoreCase(Builder.AMAZONUSERREVIEW)
				||(key.equalsIgnoreCase(Builder.AMAZONUSERREVIEWNUM))){
			return ret;
		}else if(key.equalsIgnoreCase(Builder.CAMTYPE)){
			return normalizeString(value);
		}else if(key.contains("resolution")){
			//refine resolution as concepts
			ret += this.getResolution(value);
		}else if(key.equalsIgnoreCase(Builder.USERREVIEW)){
			value = this.padString(value, PadTypes.UserRates);
			key = normalizeString(key);
			features.add(key);
			ret += "(= (role "+key+") "+normalizeString(value)+")";
		}else if(key.equalsIgnoreCase(Builder.USERREVIEWNUM)){
			value = this.padString(value, PadTypes.UserNumRates);
			key = normalizeString(key);
			features.add(key);
			ret += "(= (role "+key+") "+normalizeString(value)+")";
		}else if(key.equalsIgnoreCase(Builder.RELEASEDATE)){
			//some dates are 2009121, i.e., date with 1 
			if(value.length()<8){
//				System.out.println(value);
				value = value.substring(0, value.length()-1)+"0"+value.charAt(value.length()-1);
//				System.out.println(value);
			}
			key = normalizeString(key);
			features.add(key);
			ret += "(= (role "+key+") "+normalizeString(value)+")";
		}else
		{
			key = normalizeString(key);
			features.add(key);
			ret += "(= (role "+key+") "+normalizeString(value)+")";
		}
		return ret;
	}

	//price@name@numrRates
	//642.36@A1L9D0A8YQ4I2J_at_Amazon@14296@4.9*660.94@Amazon.com@0@0*648.00@47digital@1426@4.8*
	//sellers are instances (no nominals...)
	private static int camInstCount = 1;
	private static int spInstanceCount = 1;
	private static int vendorInstCount = 1;
	private void parsePriceVendor(String instName, String camModel, String value, StringBuilder tb,
			StringBuilder ab) {
		String camInst = normalizeString(instName);
		int instPerSeller = generateInstanceNumber(Builder.maxRandomInstancePerPriceModel, 
				Builder.randomNumCamInstancePerSeller);
		String[] pvs = null;
		//if no value at all, only generate cam1:CAM_Model
		if(value == null || value.trim().length()<1){
			for(int i=1; i<= instPerSeller; i++){
				String ind = (camInst+"_"+camInstCount++);
				totalIns ++;
				String camCp = normalizeString(camModel);
				String assertion = "(concept-assertion "+ind+" "+camCp+")\n";
				totalCA ++;
				ab.append(assertion);
			}
		}else{
			pvs = value.split("\\"+PageCrawler.seller_separator);
			for(String pv:pvs){
				if(pv.trim() == ""){
					continue;
				}
				String sp= Builder.ResellerPriceInstance+"_"+spInstanceCount++;
				totalIns ++;
				String[] items = pv.split("@");
				String sid = normalizeString(items[1]);
				String mid = "";
				if(! vendorindex.containsKey(sid)){
					//add to index
					mid = "merchant_"+vendorInstCount++;
					totalIns ++;
					vendorInstId.put(sid, mid);
					vendorindex.put(sid, items[2]+"@"+items[3]);
				}else{
					mid = vendorInstId.get(sid);
				}
				String cp = "(and "+Builder.ResellerPrice+" (= (role "+Builder.PRICE+") "+
					padString(items[0], PadTypes.PriceValue)+"))";
				String assertion = "(concept-assertion "+sp+" "+cp+")\n";
				totalCA ++;
				ab.append(assertion);
				//generate all instances
				for(int i=1; i<= instPerSeller; i++){
					String ind = (camInst+"_"+camInstCount++);
					String camCp = normalizeString(camModel);
					assertion = "(concept-assertion "+ind+" "+camCp+")\n";
					totalCA ++;
					ab.append(assertion);
					assertion = "(role-assertion "+ind+" "+sp+" (role "+Builder.hasResellerPrice+"))\n";
					totalRA ++;
					ab.append(assertion);
				}
				//generate seller info
				assertion = "(role-assertion "+sp+" "+mid+" (role "+Builder.VENDORROLE+"))\n";
				totalRA++;
				ab.append(assertion);
			}
		}
		 

		//go back to 1 for each model
		camInstCount = 1;
	}
	
	private static enum PadTypes{
		PriceValue, AmazonSellerRates, AmazonSellerNumRates, UserRates, UserNumRates, ResolutionDims,
	}
	
	private String padString(String s, PadTypes type) {
		String ret = "";
		switch(type){
		case PriceValue: 
			ret = String.format("%1$08.2f", Float.valueOf(s));
			break;
		case AmazonSellerRates:
		case UserRates:
			ret = String.format("%1$04.2f", Float.valueOf(s));
			break;
		case AmazonSellerNumRates:
		case UserNumRates:	
			ret = String.format("%1$05d", Integer.valueOf(s));
			break;
		case ResolutionDims:
			ret = String.format("%1$04d", Integer.valueOf(s));
			break;
		}

		return ret;
	}

	//if ran is true, get a random number>0 <=n, otherwise exact n instances will be created for 
	//EACH vendor selling this camera model
	public int generateInstanceNumber(int n, boolean ran){
		int ret = n;
		if(ran){
			Random generator = new Random();
			ret = generator.nextInt(n)+1;
			return ret;
		}
		return ret;
	}
	
	
	public static String normalizeString(String s){
		s = s.trim();
		String[] ori = {"\\(", "\\)", " "};
		String[] rep = {"<", ">", "_"};
		int i = 0;
		while(i < ori.length){
			s = s.replaceAll(ori[i], rep[i]);
			i++;
		}
		return s;
	}
	
	//one concept assertion for each vendor
	//also build manufacturer implications from file
	
	private void buildVendorInstances(StringBuilder tb, StringBuilder ab) {
		ab.append("\n#########################\n" +
				"### Assertions about Seller instances\n" +
				"#########################\n");
		for(String sid:vendorindex.keySet()){
			String pr = "(concept-assertion "+vendorInstId.get(sid)+" ";
			pr +="(and "+Builder.Reseller;
			String v[] = vendorindex.get(sid).split("@");
			pr += " (= (role "+Builder.VendorName+") "+sid+") ";
			pr += " (= (role "+Builder.VendorRate+") "+this.padString(v[1], PadTypes.AmazonSellerRates)+") "; 
			pr += " (= (role "+ Builder.VendorNumRate+") "+this.padString(v[0], PadTypes.AmazonSellerNumRates)+") ";
			features.add(Builder.VendorName);
			features.add(Builder.VendorNumRate);
			features.add(Builder.VendorRate);
			pr+="))\n";
			totalCA ++;
			ab.append(pr);
		}
		//get file and parse camera manufacturersas concepts
		//eg. Canon_Man -> Man and url=...
		BufferedReader in = null;
		try {
			in = new BufferedReader(new FileReader("./resources/manufacturer.wiki"));
			String strLine;
			tb.append("\n#########################\n" +
					"### Implications about Manufacturers\n" +
					"#########################\n");
			Map<String, String> countryAreas = new HashMap<String, String>();
			while ((strLine = in.readLine()) != null)   {
	        	if (strLine == null || strLine.trim().equals("")
	        			||strLine.startsWith(Display.COMMENTS)){
	        		continue;
	        	}
	        	String v[] = strLine.split(";");
	        	String vname = v[0].trim(), vurl = v[1].trim(), vloc = v[2].trim();
	        	String varea = v.length>3? v[3].trim(): null;
	        	vname = normalizeString(vname);
	        	vloc = normalizeString(vloc);
	        	if(varea!= null && !countryAreas.containsKey(vloc) && varea.length()>0){
	        		countryAreas.put(vloc, normalizeString(varea));
	        	}
	        	String pr = "(implies "+vname+" ";
				pr +="(and "+Builder.Manufacturer; 
				pr += " (= (role "+Builder.manUrl+") "+vurl+") "; 
				features.add(Builder.manUrl);
				pr += "(forall (role "+Builder.hasLoc+") "+vloc+") "; 
				pr+="))\n";
				totalAxiom ++;
				tb.append(pr);
			}
			//implications on areas
			for(String ct:countryAreas.keySet()){
				totalAxiom ++;
				tb.append("(implies "+ct+" "+countryAreas.get(ct)+")\n");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	
	/*
	 * Return format:
	 * (and (= x 1000) (= y 1000) )
	 * or,
	 * (or (and (x 1000) (y 1000)) (etc. ) )
	 */
	private String getResolution(String xy){
		//either a=one resolution or a set of resolution values sep by ,
		String ret ="";
		String[] res = this.fixResolution(xy); 
		if(res.length>1){
			ret += " (or ";
			for(int i =0; i<res.length; i++){
				ret +=  this.getXYResolution(res[i]);
			}
			ret += ")";
		}else if(res.length ==1){
			ret +=  this.getXYResolution(res[0]);
		}else{
			System.out.println("Error in paring resolution: no resolution found.");
		}
		
		return ret;
	}
	
	
	private String[] fixResolution(String xy){
		//separator is & instead of ,
		String[] res = xy.split(",");
		String[] res2 = xy.split("&");
		String[] ret = null;
		if(res2.length >1){
			//sep by &
			ret = res2;
		}else{
			//default: sep by ,
			ret = res;
		}
		List<String> my = (List<String>) java.util.Arrays.asList(ret);
		ArrayList<String> cp = new ArrayList<String> ();
		cp.addAll(my);
		for(String m:my){
			if(m == null || m.trim().length() <1){
				cp.remove(m);
				continue;
			}
			if(m.compareToIgnoreCase("n/a") == 0
					|| m.compareToIgnoreCase("Unknown") == 0
					|| m.compareToIgnoreCase("NULL") == 0)
			{
				cp.remove(m);
				cp.add(Builder.NULL);
				continue;
			}
			if(m.split("x").length==3){
				//AxBxC
				cp.remove(m);
				String[] fx = m.split("x");
				cp.add(fx[0]+" x "+fx[1]);
			}else
			if(m.split("x").length != 2){
				//likely a_b instead of "a x b"
				if(m.trim().split(" ").length == 2){
					cp.remove(m);
					String[] div = m.trim().split(" ");
					//System.err.println(m+ "resolution: "+div[0]+div[1]);
					cp.add(div[0]+ " x " + div[1]);
				}else if(m.trim().split(" ").length == 1){
					//if already removed in cp
					if(!cp.contains(m)){
						continue;
					}
					//likely one resolution is given like a,b,c x d,etc. should be a x b;
					int ind = my.indexOf(m);
					String n= my.get(ind+1);
					if(n.trim().split(" ").length == 1){
						cp.remove(m);
						cp.remove(n);
						cp.add( m + " x " +n);				
					}else{
						//only one aspect is given, cannot parse, only one possible pruning:
						//Pentax X70, given resolution 2992.
						System.err.println("Resolution cannot be resolved: "+m);
						cp.remove(m);
						continue;
					}
				}
				else{
					System.err.println("Resolution cannot be resolved: "+m);
					continue;
				}
			}
		}
		//ret =  (String[]) cp.toArray();
			ret = new String[cp.size()];
			int i=0;
			for(String c:cp){
				ret[i++] = c;
			}

		return ret;
	}
	
	
	//return a concept (conjunction of x and y)
	private String getXYResolution(String one){
		String[] ret = one.split("x");
		if(one.trim().length() < 1){
			return "";
		}
		if(one.compareToIgnoreCase("NULL") == 0){
			//in case of "n/a"
			return " "+Builder.NULL;
		}
		if(ret.length != 2){
			//solve problem axb, c,d, etc. instead of c x d			
			System.err.println("Resolution not XxY: "+one );
			return "";
		}
		ret[0] = normalizeString(ret[0]);
		ret[1] = normalizeString(ret[1]);
		//if extra symbols exist, remove the redundant part
		if(ret[0].indexOf('(')>=0 ){
			ret[0]=ret[0].substring(0, ret[0].indexOf('('));
		}
		if(ret[1].indexOf('(')>=0 ){
			ret[1]=ret[1].substring(0, ret[1].indexOf('('));
		}
		String cons = "";
		features.add(Builder.XRESOLUTION);
		features.add(Builder.YRESOLUTION);
		cons += " (and (= (role "+Builder.XRESOLUTION+") "+ this.padString(ret[0], PadTypes.ResolutionDims) +") ";
		cons += " (= (role "+Builder.YRESOLUTION+") "+ this.padString(ret[1], PadTypes.ResolutionDims) +")) ";
		return cons;
	}
	
	
}
